Skip to content

feat: api for shifting all relative past due dates#36499

Merged
e0d merged 4 commits intoopenedx:masterfrom
raccoongang:feat/api-for-shifting-all-past-due-dates
Oct 8, 2025
Merged

feat: api for shifting all relative past due dates#36499
e0d merged 4 commits intoopenedx:masterfrom
raccoongang:feat/api-for-shifting-all-past-due-dates

Conversation

@kyrylo-kh
Copy link
Member

@kyrylo-kh kyrylo-kh commented Apr 8, 2025

Description

Introduce a new API endpoint to shift all relative past due dates. This endpoint processes all user enrollments and resets self-paced due dates accordingly. This functionality is required for the mobile application, where the "Dates" tab aggregates due dates from all courses. The app includes a button that allows users to shift all past due dates in one action.

The new endpoint is available at: <LMS>/api/course_experience/v1/reset_all_course_deadlines/

Testing instructions

  1. Create a course and select the "self-paced" mode
  2. Add graded subsections to the course.
  3. In Advanced Settings, set the "Relative weeks due" to any desired number of weeks.
  4. Enroll in the course.
  5. The initial due date will be calculated as the enrollment date plus the number of weeks you specified. Wait until this due date passes.
  6. Send a POST request to <LMS>/api/course_experience/v1/reset_all_course_deadlines/ with an empty request body. This API call will automatically update all past due dates for self-paced courses with relative week configurations, shifting them based on the current date.

Deadline

None

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Apr 8, 2025
@openedx-webhooks
Copy link

Thanks for the pull request, @kyrylo-kh!

This repository is currently maintained by @openedx/wg-maintenance-edx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Apr 8, 2025
@kyrylo-kh kyrylo-kh force-pushed the feat/api-for-shifting-all-past-due-dates branch 2 times, most recently from 2a98593 to 4394424 Compare April 8, 2025 10:08
@kyrylo-kh kyrylo-kh changed the title feat: api for shifting all relaitve past due dates feat: api for shifting all relative past due dates Apr 8, 2025
@kyrylo-kh kyrylo-kh force-pushed the feat/api-for-shifting-all-past-due-dates branch from 4394424 to 68de318 Compare April 8, 2025 13:20
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Ready for Review in Contributions Apr 14, 2025
@mphilbrick211 mphilbrick211 added the needs reviewer assigned PR needs to be (re-)assigned a new reviewer label Apr 14, 2025
@kyrylo-kh kyrylo-kh force-pushed the feat/api-for-shifting-all-past-due-dates branch from 68de318 to fd5fc29 Compare May 21, 2025 16:32
@kyrylo-kh
Copy link
Member Author

@e0d The research_event_data is passed around here to provide additional context for analytics and tracking purposes. This pattern is used by analogy with the existing reset_course_deadlines function.


for course_key in course_keys:
try:
reset_deadlines_for_course(request, course_key, research_event_data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why the research_event_data is passed around here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was in the original code and not introduced here. I think this is a bug and an anti-pattern. We should open a bug to fix this after this PR merges.

)
)
@permission_classes((IsAuthenticated,))
def reset_all_relative_course_deadlines(request):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function names are changing unnecessarily across the three cases.

The original was/is:

def reset_course_deadlines(request)

The refactored helper is:

def reset_deadlines_for_course(request, course_key, research_event_data={})
The new build version is:

def reset_all_relative_course_deadlines(request)

I think we should have something like:

def reset_course_deadlines(request)
def reset_course_deadlines(request, course_key, research_event_data={})
def reset_all_course_deadlines(request)

I think it would probably be useful to have the core function of resetting in bulk be:

def reset_bulk_course_deadlines(coursekey_list)

This would make it available without passing in a request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have something like:

def reset_course_deadlines(request)
def reset_course_deadlines(request, course_key, research_event_data={})
def reset_all_course_deadlines(request)

We can’t actually have both in the same file, because Python will just override the first with the second.

Did you mean:

  • rename the view reset_all_relative_course_deadlines to reset_all_course_deadlines
  • rename the util reset_deadlines_for_course to reset_bulk_course_deadlines(coursekey_list)?

Just want to confirm before refactoring, because right now reset_deadlines_for_course is request-aware (getting course_detail). I can leave request in parameters and use coursekey_list for iterating.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I think that that naming convention you suggest makes sense. Ideally, I think, we wouldn't pass the request into the utility method because it couples it to the request cycle. However, this code is already messy because we're passing around the research event which is definitely non-standard, probably an anti-pattern.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made changes in this commit - 163c7d1

@mphilbrick211 mphilbrick211 moved this from Ready for Review to In Eng Review in Contributions Aug 21, 2025
@mphilbrick211 mphilbrick211 removed the needs reviewer assigned PR needs to be (re-)assigned a new reviewer label Aug 21, 2025
@kyrylo-kh kyrylo-kh force-pushed the feat/api-for-shifting-all-past-due-dates branch from 9537e66 to 163c7d1 Compare September 19, 2025 13:12
@mphilbrick211 mphilbrick211 added the FC Relates to an Axim Funded Contribution project label Sep 24, 2025
Copy link
Contributor

@e0d e0d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest changes address my comments. The test coverage issue still needs attention.

@kyrylo-kh kyrylo-kh force-pushed the feat/api-for-shifting-all-past-due-dates branch from 163c7d1 to 3c31cdb Compare October 3, 2025 11:43
@@ -17,6 +21,11 @@
reset_course_deadlines,
name='course-experience-reset-course-deadlines'
),
re_path(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: There's no reason for this to be an re_path just use the standard path which.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Comment on lines +81 to +87
@authentication_classes(
(
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser,
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need BearerAuthentication instead of just using the defalut JWT and Session authentication endpoints?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it by analogy with a similar existing view (reset_course_deadlines). Additionally, mobile clients use bearer authorization, so BearerAuthenticationAllowInactiveUser is needed to support that alongside the default JWT and session authentication. For now it's used only on mobile application (bearer), but in future it also can be used on web (jwt+session)

Comment on lines +106 to +108
success_course_keys, failed_course_keys = reset_bulk_course_deadlines(
request, course_keys, research_event_data
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it could take a long time based on the size of the course list. Shouldn't this be a background(celery) task? Have we looked at the performance for 10,100,1000 courses and determined this is sufficiently fast?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs pretty fast since it only updates existing schedules with a new start date - there’s no heavy logic or data creation, here is source code of main function which resets deadline:

def reset_self_paced_schedule(user, course_key, use_enrollment_date=False):
    """
    Reset the user's schedule if self-paced.

    It does not create a new schedule, just resets an existing one.
    This is used, for example, when a user requests it or when an enrollment mode changes.

    Arguments:
        user (User)
        course_key (CourseKey or str)
        use_enrollment_date (bool): if False, reset to now, else reset to original enrollment creation date
    """
    with transaction.atomic(savepoint=False):
        try:
            schedule = Schedule.objects.select_related('enrollment', 'enrollment__course').get(
                enrollment__user=user,
                enrollment__course__id=course_key,
                enrollment__course__self_paced=True,
            )
        except Schedule.DoesNotExist:
            return

        if use_enrollment_date:
            new_start_date = schedule.enrollment.created
        else:
            new_start_date = datetime.datetime.now(pytz.utc)

        # Make sure we don't start the clock on the learner's schedule before the course even starts
        new_start_date = max(new_start_date, schedule.enrollment.course.start)

        schedule.start_date = new_start_date
        schedule.save()

Even if a user had a large number of courses, it should still complete quickly because each operation is just a small database update. Also, it’s very unlikely a single user would have hundreds of self-paced courses that all need to be reset.

@kyrylo-kh
Copy link
Member Author

@e0d looks like there’s an issue with the test environment - it’s the second time we’re seeing random test failures across different suites.

Error: Failed to CreateArtifact: Received non-retryable error: Failed request: (409) Conflict: an artifact with this name already exists on the workflow run

@e0d e0d merged commit ed2e77e into openedx:master Oct 8, 2025
65 checks passed
@github-project-automation github-project-automation bot moved this from In Eng Review to Done in Contributions Oct 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FC Relates to an Axim Funded Contribution project open-source-contribution PR author is not from Axim or 2U

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

6 participants